/*******************************************************************************
* Copyright (c) 2016, 2017 Sebastian Stenzel and others.
* This file is licensed under the terms of the MIT license.
* See the LICENSE.txt file for more info.
*
* Contributors:
* Sebastian Stenzel - initial API and implementation
*******************************************************************************/
package org.cryptomator.ui.controls;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javafx.geometry.Insets;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.ListCell;
import javafx.scene.image.Image;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderImage;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
import javafx.scene.layout.CornerRadii;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
class DraggableListCell<T> extends ListCell<T> {
private static final double DROP_LINE_WIDTH = 4.0;
private static final Paint DROP_LINE_COLOR = Color.gray(0.0, 0.6);
private final List<BorderStroke> defaultBorderStrokes;
private final List<BorderImage> defaultBorderImages;
public DraggableListCell() {
setOnDragDetected(this::onDragDetected);
setOnDragOver(this::onDragOver);
setOnDragEntered(this::onDragEntered);
setOnDragExited(this::onDragExited);
setOnDragDropped(this::onDragDropped);
setOnDragDone(DragEvent::consume);
this.defaultBorderStrokes = this.getBorder() == null ? Collections.emptyList() : this.getBorder().getStrokes();
this.defaultBorderImages = this.getBorder() == null ? Collections.emptyList() : this.getBorder().getImages();
}
private Border createDropPositionBorder(double verticalCursorPosition) {
boolean isUpperHalf = verticalCursorPosition < this.getHeight() / 2.0;
final double topBorder = isUpperHalf ? DROP_LINE_WIDTH : 0.0;
final double bottomBorder = !isUpperHalf ? DROP_LINE_WIDTH : 0.0;
final BorderWidths borderWidths = new BorderWidths(topBorder, 0.0, bottomBorder, 0.0);
final BorderStroke dragStroke = new BorderStroke(DROP_LINE_COLOR, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, borderWidths, Insets.EMPTY);
final List<BorderStroke> strokes = new ArrayList<BorderStroke>(defaultBorderStrokes);
strokes.add(0, dragStroke);
return new Border(strokes, defaultBorderImages);
}
private void onDragDetected(MouseEvent event) {
if (getItem() == null) {
return;
}
final ClipboardContent content = new ClipboardContent();
content.putString(Integer.toString(getIndex()));
final Image snapshot = this.snapshot(new SnapshotParameters(), null);
final Dragboard dragboard = startDragAndDrop(TransferMode.MOVE);
dragboard.setDragView(snapshot);
dragboard.setContent(content);
event.consume();
}
private void onDragOver(DragEvent event) {
if (getItem() == null) {
return;
}
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
event.acceptTransferModes(TransferMode.MOVE);
setBorder(createDropPositionBorder(event.getY()));
}
event.consume();
}
private void onDragEntered(DragEvent event) {
if (getItem() == null) {
return;
}
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
setBorder(createDropPositionBorder(event.getY()));
}
}
private void onDragExited(DragEvent event) {
if (getItem() == null) {
return;
}
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getGestureSource() != this && event.getDragboard().hasString()) {
setBorder(new Border(defaultBorderStrokes, defaultBorderImages));
}
}
private void onDragDropped(DragEvent event) {
if (getItem() == null) {
return;
}
if (event.getGestureSource() instanceof DraggableListCell<?> && event.getDragboard().hasString()) {
final List<T> list = getListView().getItems();
try {
// where to insert what?
final int draggedIdx = Integer.parseInt(event.getDragboard().getString());
final T currentItem = this.getItem();
final T draggedItem = list.remove(draggedIdx);
final int currentItemIdx = list.indexOf(currentItem);
// insert before or after currentItem?
boolean insertBefore = event.getY() < this.getHeight() / 2.0;
final int insertPosition = insertBefore ? currentItemIdx : currentItemIdx + 1;
// insert!
getListView().getItems().add(insertPosition, draggedItem);
getListView().getSelectionModel().select(insertPosition);
event.setDropCompleted(true);
} catch (NumberFormatException e) {
event.setDropCompleted(false);
}
}
event.consume();
}
}